﻿using Abp.AspNetCore;
using Abp.Castle.Logging.Log4Net;
using Abp.EntityFrameworkCore;
using Abp.Extensions;
using Castle.Facilities.Logging;
using Hicap.AuthorizationServer.Core;
using Hicap.AuthorizationServer.Core.Notify;
using Hicap.AuthorizationServer.EntityFrameworkCore;
using Hicap.AuthorizationServer.Web.Consul;
using Hicap.AuthorizationServer.Web.IdentityServer;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Cors;
using Microsoft.AspNetCore.Mvc.Cors.Internal;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Logging;
using Swashbuckle.AspNetCore.Swagger;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.IO;
using System.Linq;
using System.Security.Cryptography.X509Certificates;

namespace Hicap.AuthorizationServer.Web.Startup
{
    public class Startup
    {
        private const string DefaultCorsPolicyName = "localhost";
        private readonly IConfigurationRoot _appConfiguration;

        public Startup(IHostingEnvironment env)
        {
            _appConfiguration = env.GetAppConfiguration();
        }

        public IServiceProvider ConfigureServices(IServiceCollection services)
        {
            AddLogging(services);

            //Configure DbContext
            AddAbpDbContext(services);

            AddTransient(services);

            AddIdentityServer(services);

            AddAuthentication(services);

            AddSignalR(services);

            // Configure CORS
            AddCors(services);

            AddMvcCore(services);

            AddSwagger(services);

            AddConsul(services);

            return AddAbp(services);
        }

        private void AddLogging(IServiceCollection services)
        {
            services.AddLogging();
        }

        private IServiceProvider AddAbp(IServiceCollection services)
        {
            //Configure Abp and Dependency Injection
            return services.AddAbp<AuthorizationServerWebModule>(options =>
            {
                //Configure Log4Net logging
                options.IocManager.IocContainer.AddFacility<LoggingFacility>(
                                    f => f.UseAbpLog4Net().WithConfig("log4net.config")
                                );
            });
        }

        private void AddSignalR(IServiceCollection services)
        {
            services.AddSignalR(x =>
            {
                x.EnableDetailedErrors = true;
            });
        }

        private void AddMvcCore(IServiceCollection services)
        {
            services.AddMvcCore(options =>
            {
                options.Filters.Add(new CorsAuthorizationFilterFactory(DefaultCorsPolicyName));
                options.Filters.Add(new HicapAuthorizationFilter());
            }).AddApiExplorer().AddAuthorization().AddJsonFormatters();
        }

        private void AddAbpDbContext(IServiceCollection services)
        {
            services.AddAbpDbContext<AuthorizationServerDbContext>(options =>
            {
                DbContextOptionsConfigurer.Configure(options.DbContextOptions, options.ConnectionString);
            });
        }

        private void AddConsul(IServiceCollection services)
        {
            services.AddConsul(x =>
            {
                x.WithHost(
                    $"http://{_appConfiguration["ConsulConfiguration:Host"]}:{_appConfiguration["ConsulConfiguration:Port"]}");
            });
        }

        private void AddIdentityServer(IServiceCollection services)
        {
            X509Certificate2 cert = new X509Certificate2(Path.Combine(Directory.GetCurrentDirectory(), "cert",
                    _appConfiguration["Certificates:CerPath"]),
                _appConfiguration["Certificates:Password"]);

            IdentityModelEventSource.ShowPII = true;

            services.AddIdentityServer(option =>
                {
                    string sysServiceName = GetServiceName(_appConfiguration);
                    int sysServicePort = GetServicePort(_appConfiguration);

                    option.IssuerUri = $"http://{sysServiceName}:{sysServicePort}";
                })
                //                .AddDeveloperSigningCredential()
                .AddSigningCredential(cert)
                .AddInMemoryIdentityResources(Config.GetIdentityResourceResources())
                .AddInMemoryApiResources(Config.GetApiResources())
                .AddInMemoryClients(Config.GetClients())
                .AddResourceOwnerValidator<ResourceOwnerPasswordValidator>()
                .AddProfileService<ProfileService>();
        }

        private void AddCors(IServiceCollection services)
        {
            services.AddCors(
                options => options.AddPolicy(
                    DefaultCorsPolicyName,
                    builder => builder
                            .WithOrigins(_appConfiguration["App:CorsOrigins"].Split(",", StringSplitOptions.RemoveEmptyEntries)
                                .Select(o => o.RemovePostFix("/"))
                                .ToArray())
                            .AllowAnyHeader()
                            .AllowAnyMethod()
                            .AllowCredentials()
                )
            );
        }

        private void AddTransient(IServiceCollection services)
        {
            services.AddTransient<CorsAuthorizationFilter, CorsAuthorizationFilter>();
            services.AddTransient<HicapAuthorizationFilter, HicapAuthorizationFilter>();
        }

        private void AddAuthentication(IServiceCollection services)
        {
            JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddIdentityServerAuthentication(options =>
                {
                    options.RequireHttpsMetadata = false; // for dev env
                    options.Authority =
                        $"http://{_appConfiguration["Identity:IP"]}:{_appConfiguration["Identity:Port"]}";
                    options.ApiName = AuthorizationServerConsts.ApiResourceNameList.FirstOrDefault(); // match with
                    options.ApiSecret = AuthorizationServerConsts.TokenSecurityKey.FirstOrDefault();
                    options.TokenRetriever = CustomTokenRetriever.FromHeaderAndQueryString;
                    options.Validate(JwtBearerDefaults.AuthenticationScheme);
                });
        }

        private void AddSwagger(IServiceCollection services)
        {
            services.AddSwaggerGen(options =>
            {
                options.SwaggerDoc("v1", new Info { Title = "Hicap.AuthorizationServer API", Version = "v1" });
                options.DocInclusionPredicate((docName, description) => true);

                // Define the BearerAuth scheme that's in use
                options.AddSecurityDefinition("bearerAuth", new ApiKeyScheme()
                {
                    Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"",
                    Name = "Authorization",
                    In = "header",
                    Type = "apiKey"
                });
            });
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IApplicationLifetime lifetime)
        {
            app.UseAbp(); //Initializes ABP framework.

            app.UseStaticFiles();

            app.UseCors(DefaultCorsPolicyName);

            app.UseIdentityServer();

            app.UseMvcWithDefaultRoute();

            app.UseAuthentication();

            app.UseSignalR(config =>
            {
                config.MapHub<NotifyHub>("/notify");
            });

            app.UseDeveloperExceptionPage();
            app.UseDatabaseErrorPage();

            // Enable middleware to serve generated Swagger as a JSON endpoint
            app.UseSwagger();
            // Enable middleware to serve swagger-ui assets (HTML, JS, CSS etc.)
            app.UseSwaggerUI(options =>
            {
                options.InjectJavascript("/swagger/ui/on-complete.js");
                options.SwaggerEndpoint("/swagger/v1/swagger.json", "Hicap.AuthorizationServer API V1");
            });
            app.UseExceptionHandler("/Error");

            app.UseConsul(lifetime, option =>
            {
                string sysServiceName = GetServiceName(_appConfiguration);
                int sysServicePort = GetServicePort(_appConfiguration);

                option.WithSelfHost($"http://{sysServiceName}:{sysServicePort}");
                option.WithServerName(sysServiceName);
            });
        }

        private static string GetServiceName(IConfiguration configuration)
        {
            string sysServiceName = Environment.GetEnvironmentVariable("HicapServiceName");

            if (string.IsNullOrEmpty(sysServiceName))
                sysServiceName = configuration["ConsulConfiguration:ServerName"];

            if (string.IsNullOrEmpty(sysServiceName))
                sysServiceName = configuration["SelfHost:Host"];
            return sysServiceName;
        }

        private static int GetServicePort(IConfiguration configuration)
        {
            string sysServicePort = Environment.GetEnvironmentVariable("HicapServicePort");

            if (string.IsNullOrEmpty(sysServicePort))
                sysServicePort = configuration["ConsulConfiguration:ServerPort"];

            if (string.IsNullOrEmpty(sysServicePort))
                sysServicePort = configuration["SelfHost:Port"];
            return Convert.ToInt32(sysServicePort);
        }
    }
}